import json
import re

from django.conf import settings
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.cache import cache
from django.core.paginator import Paginator
from django.db import DatabaseError
from django.http import response
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views import View
from django_redis import get_redis_connection
from datetime import datetime
from itsdangerous import BadData

from apps.goods.models import SKU
from apps.goods.utils import merge_carts
from .models import User, Area, Address
from .utils import LoginRequiredJsonMixin, generate_verify_email_url
from libs.celery_tasks.sendemail.tasks import send_verify_email
from utils.response_code import RETCODE
from utils.secret import serializer


class RegisterView(View):
    """用户注册"""

    def get(self,request):
        """
        注册视图
        :param request: 用户请求
        :return: 注册页面
        """
        return render(request, 'register.html')

    def post(self, request):
        """
        实现注册功能
        :param request: 用户请求
        :return: 注册结果
        """
        # 接受请求,提取参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')
        sms_code = request.POST.get('sms_code')
        allow = request.POST.get('allow')
        # 校验参数
        if not all([username, password, password2, mobile, allow]):
            return response.HttpResponseForbidden('注册失败,校验参数')
        # 校验用户名
        if not re.match(r"^[a-zA-Z0-9_]{5,20}$", username):
            return response.HttpResponseForbidden('注册失败,校验用户名')
        # 校验密码
        if not re.match(r"^[a-zA-Z0-9_]{8,20}$", password):
            return response.HttpResponseForbidden('注册失败,校验密码')
        # 校验二次密码相同
        if password2 != password:
            return response.HttpResponseForbidden('注册失败,校验二次密码相同')
        # 校验手机号是否合法
        if not re.match(r"^1[3-9]\d{9}$", mobile):
            return response.HttpResponseForbidden('注册失败,校验手机号是否合法')
        # 检查短信验证码
        if not sms_code:
            return response.HttpResponseForbidden('注册失败,短信验证码填写错误')
        sms_code_cli = get_redis_connection('sms_code')
        redis_sms_code = sms_code_cli.get(f"sms_{mobile}")
        if not redis_sms_code:
            return render(request, 'register.html', {'sms_code_errmsg':'短信验证码已过期,请重新获取!'})
        if redis_sms_code.decode() != sms_code:
            return render(request, 'register.html', {'sms_code_errmsg': '短信验证码错误,请检查后重试!'})
        # 校验是否勾选协议
        if allow != 'on':
            return response.HttpResponseForbidden('注册失败,校验是否勾选协议')
        # 保存注册数据
        try:
            user = User.objects.create_user(username=username, password=password, mobile=mobile)
        except DatabaseError as e:
            settings.LOGGER.error(e)
            return render(request, 'register.html', {'register_message': '注册失败'})
        # 状态保持
        login(request, user)
        # 响应注册结果
        # 返回主页视图
        cur_response = redirect(reverse('contents:index'))
        cur_response.set_cookie('username', username, max_age=60 * 60 * 24 * 14)
        return cur_response


class UsernameCountView(View):
    """检查用户名重复"""
    def get(self, request, username):
        # 1 url匹配中已通过正则校验
        # 2 查询数据库
        count = User.objects.filter(username=username).count()
        # print(count)

        return response.JsonResponse({'code':RETCODE.OK, 'errmsg':'ok', 'count':count})


class MobileCountView(View):
    """检查手机号是否重复"""
    def get(self, request, mobile):
        count = User.objects.filter(mobile=mobile).count()
        return response.JsonResponse({'code':RETCODE.OK, 'errmsg':'ok', 'count':count})


class LoginView(View):
    """登陆视图"""
    def get(self, request):
        """返回登陆视图"""
        return render(request, 'login.html')

    def post(self, request):
        """登陆校验功能"""
        # 1 解析参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        remembered = request.POST.get('remembered')  # 是否记住登陆状态

        # 校验参数
        if not all([username, password, remembered]):
            return response.HttpResponseForbidden('非法请求, 参数错误')

        if not re.match(r'[a-zA-Z0-9_-]{5,20}$', username):
            return response.HttpResponseForbidden('非法请求, 用户名或密码错误')
        if not re.match(r'[a-zA-Z0-9_]{8,20}$', password):
            return response.HttpResponseForbidden('非法请求, 用户名或密码错误')

        # 权限校验
        user = authenticate(request, username=username, password=password)

        if not user:  # 权限校验失败
            return render(request, 'login.html', {'account_errmsg':'用户名或密码错误'})

        # 验证通过,登入,保持登陆状态
        login(request, user)

        # 是否记住登陆
        if remembered != 'on':
            # 不记住用户名,页面关闭 绘画结束
            request.session.set_expiry(0)
        else:
            # 记住用户名,默认保存14day
            request.session.set_expiry(None)

        next_page = request.GET.get('next')
        if next_page:
            cur_url = next_page
        else:
            cur_url = reverse('contents:index')
        # 返回主页视图
        cur_response = redirect(cur_url)
        cur_response.set_cookie('username', user.username, max_age=60*60*24*14)

        # 合并cookie中的购物车数据
        cur_response = merge_carts(user, request, cur_response)
        return cur_response


class LogoutView(View):
    """退出登陆"""

    def get(self, request):
        # 1 logout 会自动清理session
        logout(request)
        # 2 清除cookie中的username
        response = redirect(reverse('contents:index'))  # 推出重定向到首页
        response.delete_cookie('username')

        return response


# 用户中心视图 LoginRequiredMixin使用,未登录则重定向到LOGIN_URL
class UserInfoView(LoginRequiredMixin, View):

    def get(self, request):
        # 返回渲染页面用户基本信息数据
        context = {
            'username': request.user.username,
            'mobile': request.user.mobile,
            'email': request.user.email,
            'email_active': request.user.email_active
        }
        return render(request, 'user_center_info.html', context)


# 增加email视图
class EmailView(LoginRequiredJsonMixin, View):
    # PUT http://www.meiduo.site:8000/emails/
    def put(self, request):
        # 解析email,put传递的参数在body中,json_dict格式, 校验正则
        email = json.loads(request.body.decode()).get('email')
        if not re.match(r'^[a-z0-9]([a-z0-9]*([.\-_]?[a-z0-9])*)*@([a-z0-9]*([\-_]?[a-z0-9])*)+(\.[a-z]{2,5}){1,2}$', email):
            return response.JsonResponse({'code': RETCODE.EMAILERR, 'errmsg': '邮箱格式错误'})

        try:
            request.user.email = email
            request.user.save()
        except DatabaseError as e:
            settings.LOGGER.error('email写入错误:', e)
            return response.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '添加邮箱失败'})

        # 发送邮件 使用celery异步发送
        verify_url = generate_verify_email_url(request.user)
        send_verify_email.delay(email, verify_url)
        return response.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加邮箱成功'})


# email激活
class EmailVerificationView(View):
    def get(self, request):
        # 解析token, 校验
        try:
            user_dict = serializer.loads(request.GET.get('token'))
        except BadData as e:
            settings.LOGGER.error('email token解密失败')
            return response.HttpResponseForbidden('无效的token')

        try:
            userid, email = user_dict.get('id'), user_dict.get('email')
            user = User.objects.get(id=userid, email=email)
        except Exception as e:
            settings.LOGGER.error('无效的token', e)
            return response.HttpResponseForbidden('无效的token')

        # 校验通过,将user的email_active激活
        user.email_active = True
        user.save()

        # 激活完毕,重定向到首页,保持登陆
        # login(request, user)
        cur_response = redirect(reverse('users:userinfo'))
        # cur_response.set_cookie(key='username', value=user.username, max_age=14*3600*24)
        return cur_response


# 收货地址
class AddressView(LoginRequiredMixin, View):
    def get(self, request):
        """提供收货地址页面视图"""
        login_user = request.user
        addresses = login_user.addresses.filter(is_deleted=False)
        address_dict_list=[
            {
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,
                "city": address.city.name,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email
            } for address in addresses
        ]
        context = {
            'default_address_id': login_user.default_address_id,
            'addresses': address_dict_list,
        }
        return render(request, 'user_center_site.html', context)


# 提供省市区数据
class AreasView(LoginRequiredJsonMixin, View):
    def get(self, request):
        """"""
        area_id = request.GET.get('area_id')
        if area_id is None:
            """{
              "code":"0",
              "errmsg":"OK",
              "province_list":[
                  {
                      "id":110000,
                      "name":"北京市"
                  },]"""
            # 先到缓存里拿
            province_list = cache.get('province_list')
            if not province_list:  # 缓存里面没有,去查数据库
                try:
                    provinces = Area.objects.filter(parent_id=None)
                    province_list = [{"id":province.id, "name":province.name} for province in provinces]
                except DatabaseError as e:
                    settings.LOGGER.error(f'Area查表错误{area_id},错误{e}')
                    return response.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '省份数据错误'})

                # 设置存储缓存
                cache.set(key='province_list', value=province_list, timeout=2*24*60*60, version=None)
            return response.JsonResponse({"code": "0", "errmsg": "OK", "province_list": province_list})

        # 提供市或区数据
        # {
        #   "code":"0",
        #   "errmsg":"OK",
        #   "sub_data":{
        #       "id":130000,
        #       "name":"河北省",
        #       "subs":[
        #           {
        #               "id":130100,
        #               "name":"石家庄市"
        #           },]
        #     }
        # }
        sub_data = cache.get(f'sub_{area_id}')
        if not sub_data:
            try:
                name = Area.objects.get(id=area_id)
                sub_data_obj = Area.objects.filter(parent_id=area_id)
            except DatabaseError as e:
                settings.LOGGER.error(f'Area查表错误{area_id},错误{e}')
                return response.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '城市或区数据错误'})

            sub_data = {
                "id": area_id,
                "name": name.name,
                "subs": [{"id": sub.id, "name": sub.name} for sub in sub_data_obj]
            }

            # 设置市/区缓存,注意key的唯一性
            cache.set(key=f'sub_{area_id}', value=sub_data, timeout=2 * 24 * 60 * 60, version=None)

        return response.JsonResponse({"code": "0", "errmsg": "OK", "sub_data": sub_data})


# 新增收货地址
class AddAddressView(LoginRequiredJsonMixin, View):
    def post(self, request):
        # 首先校验用户地址总数是否超过上限
        if request.user.addresses.count() >= 20:
            return response.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '超过地址数量上限'})
        # 接受参数,解析参数 JSON
        json_dict   = json.loads(request.body)
        receiver	= json_dict.get('receiver')     # string	是	收货人
        province_id	= json_dict.get('province_id')  # string	是	省份ID
        city_id	    = json_dict.get('city_id')      # string	是	城市ID
        district_id	= json_dict.get('district_id')  # string	是	区县ID
        place	    = json_dict.get('place')        # string	是	收货地址
        mobile	    = json_dict.get('mobile')       # string	是	手机号
        tel	        = json_dict.get('tel')          # string	否	固定电话
        email	    = json_dict.get('email')        # string	否	邮箱

        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return response.HttpResponseForbidden("非法请求, 缺少必传参数")
        # 校验正则,
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return response.HttpResponseForbidden("非法请求, 手机号错误")
        if tel and not re.match(r'^(0\d{2,3}[\s\-]?)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
            return response.HttpResponseForbidden("非法请求, 电话号码错误")
        if email and not re.match(r'^[a-z0-9]([a-z0-9]*([.\-_]?[a-z0-9])*)*@([a-z0-9]*([\-_]?[a-z0-9])*)+(\.[a-z]{2,5}){1,2}$', email):
            return response.HttpResponseForbidden("非法请求, 邮箱错误")

        # 校验通过,保存地址
        try:
            user_addr = Address.objects.create(
                user=request.user,
                title=receiver,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email,
            )

        # 用户defanlt_address为null,则将新增的地址设为默认
            if not request.user.default_address_id:
                request.user.default_address_id = user_addr.id
                request.user.save()
        except DatabaseError as e:
            settings.LOGGER.error('用户新增地址写入失败', e)
            return response.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '添加地址失败'})

        # 返回前端json数据,局部刷新页面
        address_dict = {

            "id": user_addr.id,
            "title": user_addr.title,
            "receiver": user_addr.receiver,
            "province": user_addr.province.name,
            "city": user_addr.city.name,
            "district": user_addr.district.name,
            "place": user_addr.place,
            "mobile": user_addr.mobile,
            "tel": user_addr.tel,
            "email": user_addr.email

        }
        return response.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加地址成功', 'address':address_dict})


# 修改/删除 收货地址
class EditAddressView(LoginRequiredJsonMixin, View):
    """修改地址信息"""
    # put方法修改地址, 提交json格式数据, form表单提交修该的数据, address_id在url中提取
    def put(self,request,  address_id):
        # 解析校验参数
        json_dict   = json.loads(request.body)
        receiver	= json_dict.get('receiver')     # string	是	收货人
        province_id	= json_dict.get('province_id')  # string	是	省份ID
        city_id	    = json_dict.get('city_id')      # string	是	城市ID
        district_id	= json_dict.get('district_id')  # string	是	区县ID
        place	    = json_dict.get('place')        # string	是	收货地址
        mobile	    = json_dict.get('mobile')       # string	是	手机号
        tel	        = json_dict.get('tel')          # string	否	固定电话
        email	    = json_dict.get('email')        # string	否	邮箱

        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return response.HttpResponseForbidden("非法请求, 缺少必传参数")
        # 校验正则,
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return response.HttpResponseForbidden("非法请求, 手机号错误")
        if tel and not re.match(r'^(0\d{2,3}[\s\-]?)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
            return response.HttpResponseForbidden("非法请求, 电话号码错误")
        if email and not re.match(r'^[a-z0-9]([a-z0-9]*([.\-_]?[a-z0-9])*)*@([a-z0-9]*([\-_]?[a-z0-9])*)+(\.[a-z]{2,5}){1,2}$', email):
            return response.HttpResponseForbidden("非法请求, 邮箱错误")

        # 修改参数,对应的address_id
        try:
            addr = Address.objects.get(id=address_id, is_deleted=False)
            addr.receiver = receiver
            addr.province_id = province_id
            addr.city_id = city_id
            addr.district_id = district_id
            addr.place = place
            addr.mobile = mobile
            addr.tel = tel
            addr.email = email
            addr.save()
        except DatabaseError as e:
            settings.LOGGER.error('修改收货地址错误', e)
            return response.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '修改地址失败'})

        address_dict = {
            "id": address_id,
            "title": addr.title,
            "receiver": receiver,
            "province": addr.province.name,
            "city": addr.city.name,
            "district": addr.district.name,
            "place": place,
            "mobile": mobile,
            "tel": tel,
            "email": email
        }
        return response.JsonResponse({'code': RETCODE.OK, 'errmsg': '修改地址成功', 'address': address_dict})

    def delete(self, request, address_id):
        """删除地址"""
        try:
            user_addr = Address.objects.get(id=address_id)
            user_addr.is_deleted = True
            user_addr.save()
        except DatabaseError as e:
            settings.LOGGER.error('逻辑删除地址失败', e)
            return response.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '删除地址失败'})

        return response.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除地址成功'})


# 设置默认地址
class SetDefaultAddressView(LoginRequiredJsonMixin, View):
    """设置默认地址"""
    def put(self, request, address_id):
        try:
            addr = Address.objects.get(id=address_id, is_deleted=False)
            request.user.default_address_id = addr.id
            request.user.save()
        except DatabaseError as e:
            settings.LOGGER.error('设置默认地址失败', e)
            return response.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '设置默认地址失败'})

        return response.JsonResponse({'code': RETCODE.OK, 'errmsg': '设置默认地址成功'})


# 修改地址名称
class EditAddressTitleView(LoginRequiredJsonMixin, View):
    """修改地址标题"""
    def put(self, request, address_id):
        new_title = json.loads(request.body).get('title')
        try:
            addr = Address.objects.get(id=address_id, is_deleted=False)
            addr.title = new_title
            addr.save()
        except DatabaseError as e:
            settings.LOGGER.error('设置地址标题失败', e)
            return response.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '设置地址标题失败'})

        return response.JsonResponse({'code': RETCODE.OK, 'errmsg': '设置地址标题成功'})


# 密码
class PwdView(LoginRequiredJsonMixin, View):
    """密码界面"""
    def get(self, request):
        return render(request, 'user_center_pass.html')

    # 修改密码
    def post(self, request):
        data_dict = request.POST
        old_pwd = data_dict.get('old_pwd')
        new_pwd = data_dict.get('new_pwd')
        new_cpwd = data_dict.get('new_cpwd')

        if not all([old_pwd, new_pwd, new_cpwd]):
            return response.HttpResponseForbidden("非法请求,参数缺失")
        if not (re.match(r'^[0-9A-Za-z]{8,20}$', old_pwd)
                and re.match(r'^[0-9A-Za-z]{8,20}$', new_pwd)
                and re.match(r'^[0-9A-Za-z]{8,20}$', new_cpwd)):
            return response.HttpResponseForbidden("非法请求,参数错误")
        if new_pwd != new_cpwd:
            return response.HttpResponseForbidden("非法请求,密码不一致")

        cur_user = request.user
        if not cur_user.check_password(old_pwd):
            return render(request, 'user_center_pass.html', {'change_pwd_errmsg': '原密码错误'})
        try:
            cur_user.set_password(new_cpwd)
            cur_user.save()
        except DatabaseError as e:
            settings.LOGGER.error('修改密码写入失败')
            return render(request, 'user_center_pass.html', {'change_pwd_errmsg': '密码修改失败'})

        # 修改成功则登出,清除cookie
        logout(request)
        cur_response = redirect(reverse('users:login'))
        cur_response.delete_cookie('username')
        cur_response.delete_cookie('sessionid')
        return cur_response


# 用户浏览历史
class BrowseHistoriesView(LoginRequiredJsonMixin, View):

    def get(self, request):
        user = request.user
        history_cli = get_redis_connection('history')
        sku_id_lst = history_cli.lrange(f"history_{user.id}", 0, 4)
        skus = []
        for sku_id in sku_id_lst:
            try:
                sku = SKU.objects.get(id=sku_id)
                skus.append({
                    "id": sku.id,
                    "name": sku.name,
                    "default_image_url": sku.default_image.url,
                    "price": sku.price,
                })
            except SKU.DoesNotExist:
                pass

        return response.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'skus': skus})

    def post(self, request):
        sku_id = json.loads(request.body).get('sku_id')

        # 获取对应的sku
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist as e:
            settings.LOGGER.error("获取浏览sku失败", e)
            return response.HttpResponseNotFound("商品不存在")

        # redis缓存用户浏览的sku_id
        user_key = f"history_{request.user.id}"
        history_cli = get_redis_connection('history')
        p_history = history_cli.pipeline()
        # 去重
        p_history.lrem(user_key, 0, sku_id)
        # 添加
        p_history.lpush(user_key, sku_id)
        # 截取
        p_history.ltrim(user_key, 0, 4)
        p_history.execute()
        return response.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})


# 用户订单列表
class UserOrderView(LoginRequiredJsonMixin, View):
    def get(self, request, page_num):
        user = request.user
        order_lst = user.orderinfo_set.order_by('-order_id')
        for order in order_lst:
            order_time = datetime.strptime(order.order_id[:14], '%Y%m%d%H%M%S')
            order_time_str = order_time.strftime('%Y-%m-%d %H:%M:%S')
            order.datetime = order_time_str

        # 创建分页器
        pages = Paginator(order_lst, 3)
        page = pages.page(page_num)
        context = {
            "order_lst": page,
            "currentPage": page_num,
            "totalPage": pages.num_pages,
        }
        return render(request, 'user_center_order.html', context)

